package com.btr.uimeter;

import java.lang.instrument.Instrumentation;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import com.btr.uimeter.config.UIMeterConfig;
import com.btr.uimeter.probes.UIMeterProbe;


/*****************************************************************************
 * The central class of the UI meter framework. Here you register probes 
 * and loggers. This glues everything together. 
 * 
 * The registered probes are responsible for gathering data. 
 * This data is then propagated to all registered loggers.
 * 
 * This class is implemented as a singleton.
 * 
 * @author Bernd Rosstauscher (java@rosstauscher.de) Copyright 2010
 ****************************************************************************/

public final class UIMeter {
	
	private List<UIMeterProbe> probeList;
	private List<UIMeterEventHandler> handlerList;
	private EventProcessor eventFetcher;
	
	private static UIMeter instance;

	/*************************************************************************
	 * Constructor
	 ************************************************************************/
	
	private UIMeter() {
		super();
		this.probeList = new CopyOnWriteArrayList<UIMeterProbe>();
		this.handlerList = new CopyOnWriteArrayList<UIMeterEventHandler>();
		this.eventFetcher = new EventProcessor();
		registerShutdownHook();
	}

	/*************************************************************************
	 * Register shutdown hook to make sure we cleanup at system exit.
	 ************************************************************************/
	
	private void registerShutdownHook() {
		Runtime.getRuntime().addShutdownHook(new ShutdownHook());
	}
	
	/*************************************************************************
	 * Needed for the java agent loading.
	 * Needs a entry in the jar manifest like this
	 * 
	 * Premain-Class: com.btr.uimeter.UIMeter
	 * 
	 * @param agentArgs
	 * @param inst
	 ************************************************************************/
	
	public static void premain(String agentArgs, Instrumentation inst) {
		// TODO rossi 14.02.2010 configure the library 
		// agentArgs names the config file if none is set load from a default file.
		UIMeterConfig.setupWithDefaultConfig();
		UIMeter.getInstance().startMonitoring();
	}
	
	/*************************************************************************
	 * Gets the one and only instance of the UIMeter.
	 * @return a UIMeter.
	 ************************************************************************/
	
	public static synchronized UIMeter getInstance() {
		if (instance == null) {
			instance = new UIMeter();
		}
		return instance;
	}
	
	/*************************************************************************
	 * Adds a new probe. This must be done before the UIMeter is started.
	 * @param probe to add.
	 ************************************************************************/
	
	public void addProbe(UIMeterProbe probe) {
		this.probeList.add(probe);
		probe.addEventHandler(this.eventFetcher);
	}
	
	/*************************************************************************
	 * Add an event handler to the UIMeter to process all generated events.
	 * @param handler to add.
	 ************************************************************************/
	
	public void addProbeEventHandler(UIMeterEventHandler handler) {
		this.handlerList.add(handler);
	}
	
	/*************************************************************************
	 * Starts all registered probes to collect the probing data. 
	 ************************************************************************/
	
	public void startMonitoring() {
		for (UIMeterProbe probe : this.probeList) {
			probe.start();
		}
	}

	/*************************************************************************
	 * Stops all registered probes.
	 ************************************************************************/
	
	public void stopMonitoring() {
		for (UIMeterProbe probe : this.probeList) {
			probe.stop();
		}
	}

	/*************************************************************************
	 * Propagates an event to all registered listener.
	 * @param producer the producer of the event
	 * @param e the event containing all probing data. 
	 ************************************************************************/
	
	private void propagateEventToListner(UIMeterProbe producer, UIMeterEvent e) {
		for (UIMeterEventHandler handler : handlerList) {
			handler.processEvent(producer, e);
		}
	}

	/*************************************************************************
	 * Shutdown and cleanup.
	 ************************************************************************/
	
	public void shutdown() {
		stopMonitoring();
		closeHandler();
	}

	/*************************************************************************
	 * Closes all registered handlers.
	 ************************************************************************/
	
	private void closeHandler() {
		for (UIMeterEventHandler handler : handlerList) {
			handler.close();
		}
	}
	
	/*****************************************************************************
	 * A VM shutdown hook used to gracefully shutdown the UIMeter and all 
	 * registered probes and handlers.   
	 ****************************************************************************/
	
	private static final class ShutdownHook extends Thread {
		/*************************************************************************
		 * run
		 * @see java.lang.Thread#run()
		 ************************************************************************/
		@Override
		public void run() {
			UIMeter.getInstance().shutdown();
		}
	}

	/*****************************************************************************
	 * Internal helper class to route all events from all probes to the listeners
	 * that are registered on the UIMeter directly.
	 ****************************************************************************/
	
	private static class EventProcessor implements UIMeterEventHandler {

		/*************************************************************************
		 * processEvent
		 * @see com.btr.uimeter.UIMeterEventHandler#processEvent(com.btr.uimeter.UIMeterProbe, com.btr.uimeter.UIMeterEvent)
		 ************************************************************************/
		
		public void processEvent(UIMeterProbe producer, UIMeterEvent e) {
			UIMeter.getInstance().propagateEventToListner(producer, e);
		}

		/*************************************************************************
		 * close
		 * @see com.btr.uimeter.UIMeterEventHandler#close()
		 ************************************************************************/
		
		public void close() {
			// not used
		}
		
	}

}
